#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "debug_utils.h"
#include "status_enum.h"
#include "student_data.h"
#include "student_interface.h"

static status_t data_delete_student(const char *key,
                                    student_info_t *info,
                                    int (*compare)(const char *, const char *));

static status_t data_query_student(const char *key,
                                   student_info_t *info,
                                   int (*compare)(const char *, const char *));

/* Define the head node of the student data linked list */
struct list_head student_list = LIST_HEAD_INIT(student_list);
/**
 * Add student information
 */
status_t data_add_student(const student_info_t *info)
{
        CHECK_NULL(info);

        DEBUG_PRINT("Adding student with ID: %s\n", info->id);

        student_node_t *new_node = (student_node_t *)
            malloc(sizeof(student_node_t));

        if (!new_node)
                return STATUS_MEMORY_ERROR;

        new_node->info = *info;

        list_add_tail(&new_node->list, &student_list);

        PRINT_STRUCT(*info);

        return STATUS_SUCCESS;
}

/**
 * Delete student information by id
 *
 */
status_t data_delete_student_id(const char *id, student_info_t *info)
{
        return data_delete_student(id, info, strcmp);
}

/**
 * Delete student information by name
 *
 */

status_t data_delete_student_name(const char *name, student_info_t *info)
{
        return data_delete_student(name, info, strcmp);
}

/**
 * Update student information
 */
status_t data_update_student(const student_info_t *info)
{
        assert(info != NULL);

        DEBUG_PRINT("Updating student with id: %s\n", info->id);

        student_node_t *pos;

        list_for_each_entry(pos, &student_list, list)
        {
                if (strcmp(pos->info.id, info->id) == 0)
                {
                        PRINT_STRUCT(pos->info);

                        if (info->name[0] != '\0')
                        {
                                strncpy(pos->info.name,
                                        info->name, sizeof(pos->info.name) - 1);
                                pos->info.name
                                    [sizeof(pos->info.name) - 1] = '\0';
                        }

                        if (info->dob[0] != '\0')
                        {
                                strncpy(pos->info.dob,
                                        info->dob, sizeof(pos->info.dob) - 1);
                                pos->info.dob
                                    [sizeof(pos->info.dob) - 1] = '\0';
                        }

                        if (info->gender != GENDER_NOT_SPECIFIED)
                                pos->info.gender = info->gender;

                        DEBUG_PRINT("Student with id: %s "
                                    "successfully updated\n",
                                    info->id);

                        return STATUS_SUCCESS;
                }
        }

        return STATUS_NOT_FOUND;
}

/**
 * Query student information by id
 */
status_t data_query_student_id(const char *id, student_info_t *info)
{
        return data_query_student(id, info, strcmp);
}

/**
 * Query student information by name
 */
status_t data_query_student_name(const char *name, student_info_t *info)
{
        return data_query_student(name, info, strcmp);
}

/**
 * Get all student information
 */
status_t data_get_all_student(student_info_t **students, size_t *count)
{
        assert(students != NULL);
        assert(count != NULL);

        size_t num_students = 0;
        student_node_t *pos;

        list_for_each_entry(pos, &student_list, list)
            num_students++;

        student_info_t *student_array = (student_info_t *)malloc(num_students * sizeof(student_info_t));

        if (student_array == NULL)
                return STATUS_MEMORY_ERROR;

        size_t index = 0;
        list_for_each_entry(pos, &student_list, list)
            student_array[index++] = pos->info;

        *students = student_array;
        *count = num_students;

        DEBUG_PRINT("Total number of students: %zu\n", num_students);
        for (size_t i = 0; i < num_students; i++)
                PRINT_STRUCT(student_array[i]);

        return STATUS_SUCCESS;
}

static status_t data_delete_student(const char *key,
                                    student_info_t *info,
                                    int (*compare)(const char *, const char *))
{
        assert(key != NULL);

        DEBUG_PRINT("Deleting student with key: %s\n", key);

        student_node_t *pos, *n;
        list_for_each_entry_safe(pos, n, &student_list, list)
        {
                if (compare(pos->info.id, key) == 0 || compare(pos->info.name, key) == 0)
                {

                        memcpy(info, &pos->info, sizeof(student_info_t));
                        list_del(&pos->list);
                        free(pos);
                        DEBUG_PRINT("Student with key: %s"
                                    " successfully deleted\n",
                                    key);
                        PRINT_STRUCT(*info);
                        return STATUS_SUCCESS;
                }
        }

        return STATUS_NOT_FOUND;
}

static status_t data_query_student(const char *key,
                                   student_info_t *info,
                                   int (*compare)(const char *, const char *))
{
        assert(key != NULL);
        assert(info != NULL);

        DEBUG_PRINT("Querying student with key: %s\n", key);

        student_node_t *pos;

        list_for_each_entry(pos, &student_list, list)
        {
                if (compare(pos->info.id, key) == 0 || compare(pos->info.name, key) == 0)
                {

                        *info = pos->info;
                        DEBUG_PRINT("Student with key: %s found\n", key);
                        PRINT_STRUCT(*info);
                        return STATUS_SUCCESS;
                }
        }

        return STATUS_NOT_FOUND;
}
